home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 25 / Cream of the Crop 25.iso / os2 / gnuwget.zip / wget-1.4.3 / src / html.c < prev    next >
C/C++ Source or Header  |  1997-02-08  |  13KB  |  517 lines

  1. /* Dealing with HTML (parsing, generating).
  2.    Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
  3.    
  4.    This program is free software; you can redistribute it and/or modify
  5.    it under the terms of the GNU General Public License as published by
  6.    the Free Software Foundation; either version 2 of the License, or
  7.    (at your option) any later version.
  8.    
  9.    This program is distributed in the hope that it will be useful,
  10.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.    GNU General Public License for more details.
  13.    
  14.    You should have received a copy of the GNU General Public License
  15.    along with this program; if not, write to the Free Software
  16.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
  17.  
  18.  
  19. #ifdef HAVE_CONFIG_H
  20. #  include <config.h>
  21. #endif /* HAVE_CONFIG_H */
  22.  
  23. #include <ctype.h>
  24. #ifdef HAVE_STRING_H
  25. #  include <string.h>
  26. #else
  27. #  include <strings.h>
  28. #endif
  29. #include <stdio.h>
  30. #include <stdlib.h>
  31. #include <sys/types.h>
  32. #include <errno.h>
  33.  
  34. #include "wget.h"
  35. #include "options.h"
  36. #include "url.h"
  37. #include "utils.h"
  38. #include "ftp.h"
  39. #include "html.h"
  40. #include "http.h"
  41.  
  42. extern struct options opt;
  43. extern int errno;
  44.  
  45. state_t global_state;
  46.  
  47. /* Match a string against a null-terminated list of identifiers. */
  48. int
  49. idmatch(tag_t *tags, const char *tag, const char *attr)
  50. {
  51.    int i;
  52.  
  53.    if (!tag || !attr)
  54.       return 0;
  55.    
  56.    for (i = 0; tags[i].tag; i++)
  57.       if (!strcasecmp(tags[i].tag, tag) && !strcasecmp(tags[i].attr, attr))
  58.      return 1;
  59.    return 0;
  60. }
  61.  
  62. /* The following function parses an HTML buffer buf, of size bufsize
  63.    searching for tags. When a tag is encountered, the routine will
  64.    extract its components. If "src", "href" or "background" are found,
  65.    their address is returned, and so is the length of the
  66.    string. Returns NULL if no URL is found.
  67.  
  68.    We use unsigned char instead of char to be able to use ctype macros
  69.    properly (as suggested by dave@fly.cc.fer.hr).  */
  70. const char *
  71. htmlfindurl(const unsigned char *buf, int bufsize, int *size, int init)
  72. {
  73.    const unsigned char *p, *ph;
  74.    state_t *s;
  75.    /* NULL-terminated list of tags and modifiers someone would want to
  76.       follow -- feel free to edit to suit your needs: */
  77.    static tag_t html_allow[] = {
  78.       { "a", "href" },
  79.       { "img", "src" },
  80.       { "img", "href" },
  81.       { "body", "background" },
  82.       { "frame", "src" },
  83.       { "iframe", "src" },
  84.       { "fig", "src" },
  85.       { "overlay", "src" },
  86.       { "applet", "code" },
  87.       { "script", "src" },
  88.       { "embed", "src" },
  89.       { "bgsound", "src" },
  90.       { "area", "href" },
  91.       { "base", "href" },       /* This one is treated specially! */
  92.       { "meta", "content" },    /* As is this one! */
  93.       { NULL, NULL }
  94.    };
  95.  
  96.    s = &global_state;
  97.    
  98.    if (init)
  99.    {
  100.       DEBUGP("Resetting a parser state.\n");
  101.       memset(s, 0, sizeof(*s));
  102.    }
  103.  
  104.    while (1)
  105.    {
  106.       if (!bufsize)
  107.      break;
  108.       /* Let's look for a tag, if we are not already in one. */
  109.       if (!s->at_value)
  110.       {
  111.      /* Find '<'. */
  112.      if (*buf != '<')
  113.         for (; bufsize && *buf != '<'; ++buf, --bufsize);
  114.      if (!bufsize)
  115.         break;
  116.      /* Skip spaces. */
  117.      for (++buf, --bufsize; bufsize && isspace(*buf) && *buf != '>';
  118.           ++buf, --bufsize);
  119.      if (!bufsize)
  120.         break;
  121.      p = buf;
  122.      /* Find the tag end. */
  123.      for (; bufsize && !isspace(*buf) && *buf != '>' && *buf != '=';
  124.           ++buf, --bufsize);
  125.      if (!bufsize)
  126.         break;
  127.      if (*buf == '=')
  128.      {
  129.         /* This is the case of <tag=something>, which is
  130.            illegal. Just skip it. */
  131.         ++buf, --bufsize;
  132.         continue;
  133.      }
  134.      if (p == buf)
  135.      {
  136.         /* This most assuredly means that *buf == '>' */
  137.         ++buf, --bufsize;
  138.         continue;
  139.      }
  140.      s->tag = strdupdelim((char *)p, (char *)buf);
  141.      if (*buf == '>')
  142.      {
  143.         free(s->tag);
  144.         s->tag = NULL;
  145.         ++buf, --bufsize;
  146.         continue;
  147.      }
  148.       }
  149.       else                      /* s->at_value */
  150.       {
  151.      /* Reset the at_value flag. */
  152.      s->at_value = 0;
  153.      /* If in quotes, just skip out of them and continue
  154.         living. */
  155.      if (s->in_quote)
  156.      {
  157.         s->in_quote = 0;
  158.         for (; bufsize && *buf != s->quote_char; ++buf, --bufsize);
  159.         if (!bufsize)
  160.            break;
  161.         ++buf, --bufsize;
  162.      }
  163.      if (!bufsize)
  164.         break;
  165.      if (*buf == '>')
  166.      {
  167.         if (s->tag)
  168.            free(s->tag);
  169.         if (s->attr)
  170.            free(s->attr);
  171.         s->tag = s->attr = NULL;
  172.         continue;
  173.      }
  174.       }
  175.       /* Find the attributes. */
  176.       do
  177.       {
  178.      if (s->attr)
  179.      {
  180.         free(s->attr);
  181.         s->attr = NULL;
  182.      }
  183.      if (!bufsize)
  184.         break;
  185.      /* Skip the spaces if we have them. We don't have them at
  186.         places like <img alt="something"src="something-else">
  187.                         ^ no spaces here. */
  188.      if (isspace(*buf))
  189.         for (++buf, --bufsize; bufsize && isspace(*buf) && *buf != '>';
  190.          ++buf, --bufsize);
  191.      if (!bufsize || *buf == '>')
  192.         break;
  193.      if (*buf == '=')
  194.      {
  195.         /* This is the case of <tag = something>, which is
  196.            illegal. Just skip it. */
  197.         ++buf, --bufsize;
  198.         continue;
  199.      }
  200.      p = buf;
  201.      /* Find the attribute end. */
  202.      for (; bufsize && !isspace(*buf) && *buf != '>' && *buf != '=';
  203.           ++buf, --bufsize);
  204.      if (!bufsize || *buf == '>')
  205.         break;
  206.      /* Construct the attribute. */
  207.      s->attr = strdupdelim((char *)p, (char *)buf);
  208.      /* Now we must skip the spaces to find '='. */
  209.      if (*buf != '=')
  210.      {
  211.         for (; bufsize && isspace(*buf) && *buf != '>'; ++buf, --bufsize);
  212.         if (!bufsize || *buf == '>')
  213.            break;
  214.      }
  215.      /* If we still don't have '=', something is amiss. */
  216.      if (*buf != '=')
  217.         continue;
  218.      /* Find the beginning of attr. value by skipping the
  219.         spaces. */
  220.      ++buf, --bufsize;
  221.      for (; bufsize && isspace(*buf) && *buf != '>'; ++buf, --bufsize);
  222.      if (!bufsize || *buf == '>')
  223.         break;
  224.      ph = NULL;
  225.      /* The value of an attribute can, but does not have to be
  226.         quoted. */
  227.      if (*buf == '\"' || *buf == '\'')
  228.      {
  229.         s->in_quote = 1;
  230.         s->quote_char = *buf;
  231.         p = buf + 1;
  232.         for (++buf, --bufsize;
  233.          bufsize && *buf != s->quote_char && *buf != '\n';
  234.          ++buf, --bufsize)
  235.            if (*buf == '#')
  236.           ph = buf;
  237.         if (!bufsize)
  238.         {
  239.            s->in_quote = 0;
  240.            break;
  241.         }
  242.         if (*buf == '\n')
  243.         {
  244.            /* Obviously no longer in quote. It might be well to
  245.           check whether '>' was encountered, but that would be
  246.           encouraging writers of invalid HTMLs, and we don't
  247.           want that, now do we? */
  248.            s->in_quote = 0;
  249.            continue;
  250.         }
  251.      }
  252.      else
  253.      {
  254.         p = buf;
  255.         for (; bufsize && !isspace(*buf) && *buf != '>'; ++buf, --bufsize)
  256.            if (*buf == '#')
  257.           ph = buf;
  258.         if (!bufsize)
  259.            break;
  260.      }
  261.      /* If '#' was found unprotected in a URI, it is probably an
  262.         HTML marker, or color spec. */
  263.      *size = (ph ? ph : buf) - p;
  264.      /* The URI is liable to be returned if:
  265.         1) *size != 0;
  266.         2) its tag and attribute are found in html_allow. */
  267.      if (*size && idmatch(html_allow, s->tag, s->attr))
  268.      {
  269.         if (!strcasecmp(s->tag, "base") && !strcasecmp(s->attr, "href"))
  270.         {
  271.            if (s->base)
  272.           free(s->base);
  273.            s->base = strdupdelim((char *)p, (char *)buf);
  274.         }
  275.         else if (!strcasecmp(s->tag, "meta") && !strcasecmp(s->attr, "content"))
  276.         {
  277.            /* Some pages use a META tag to specify that the page
  278.           be refreshed by a new page after a given number of
  279.           seconds.  We need to attempt to extract an URL for
  280.           the new page from the other garbage present.  The
  281.           general format for this is:                  
  282.           <META HTTP-EQUIV=Refresh CONTENT="0; URL=index2.html">
  283.  
  284.           So we just need to skip past the "0; URL=" garbage
  285.           to get to the URL.  META tags are also used for
  286.           specifying random things like the page author's name
  287.           and what editor was used to create it.  So we need
  288.           to be careful to ignore them and not assume that an
  289.           URL will be present at all. */
  290.            for (; *size && isdigit(*p); p++, *size -= 1);
  291.            if (*p == ';')
  292.            {
  293.           for (p++, *size -= 1; *size && isspace(*p); p++, *size -= 1) ;
  294.           if (!strncasecmp((char *)p, "URL=", 4))
  295.           {
  296.              p += 4, *size -= 4;
  297.              s->at_value = 1;
  298.              return (char *)p;
  299.           }
  300.            }
  301.         }
  302.         else
  303.         {
  304.            s->at_value = 1;
  305.            return (char *)p;
  306.         }
  307.      }
  308.      /* Exit from quote. */
  309.      if (*buf == s->quote_char)
  310.      {
  311.         s->in_quote = 0;
  312.         ++buf, --bufsize;
  313.      }
  314.       } while (*buf != '>');
  315.       if (s->tag)
  316.      free(s->tag);
  317.       if (s->attr)
  318.      free(s->attr);
  319.       s->tag = s->attr = NULL;
  320.       if (!bufsize)
  321.      break;
  322.    }
  323.  
  324.    if (s->tag)
  325.       free(s->tag);
  326.    if (s->attr)
  327.       free(s->attr);
  328.    if (s->base)
  329.       free(s->base);
  330.    memset(s, 0, sizeof(*s));    /* Just to be sure. */
  331.    DEBUGP("HTML parser ends here (state destroyed).\n");
  332.    return NULL;
  333. }
  334.  
  335. /* The function returns the base reference of HTML buffer id, or NULL
  336.    if one wasn't defined for that buffer. */
  337. const char *
  338. html_base(void)
  339. {
  340.    return global_state.base;
  341. }
  342.  
  343. /* The function returns the pointer to the malloc-ed quoted version of
  344.    string s.  It will recognize and quote numeric and special graphic
  345.    entities, as per RFC1866:
  346.  
  347.    `&' -> `&'
  348.    `<' -> `<'
  349.    `>' -> `>'
  350.    `"' -> `"'
  351.  
  352.    No other entities are recognized or replaced. */
  353. char *
  354. html_quote_string(const char *s)
  355. {
  356.    const char *b;
  357.    char *p, *res;
  358.    int i;
  359.  
  360.    b = s;
  361.    /* Pass through the string, and count the new size. */
  362.    for (i = 0; *s; s++, i++)
  363.    {
  364.       if (*s == '&')
  365.      i += 4;                /* `amp;' */
  366.       else if (*s == '<' || *s == '>')
  367.      i += 3;                /* `lt;' and `gt;' */
  368.       else if (*s == '\"')
  369.      i += 5;                /* `quot;' */
  370.    }
  371.    /* Allocate it. */
  372.    res = (char *)nmalloc(i + 1);
  373.    s = b;
  374.    for (p = res; *s; s++)
  375.    {
  376.       switch (*s)
  377.       {
  378.      case '&':
  379.         *p++ = '&';
  380.         *p++ = 'a';
  381.         *p++ = 'm';
  382.         *p++ = 'p';
  383.         *p++ = ';';
  384.         break;
  385.      case '<': case '>':
  386.         *p++ = '&';
  387.         *p++ = (*s == '<' ? 'l' : 'g');
  388.         *p++ = 't';
  389.         *p++ = ';';
  390.         break;
  391.      case '\"':
  392.         *p++ = '&';
  393.         *p++ = 'q';
  394.         *p++ = 'u';
  395.         *p++ = 'o';
  396.         *p++ = 't';
  397.         *p++ = ';';
  398.         break;
  399.      default:
  400.         *p++ = *s;
  401.       }
  402.    }
  403.    *p = '\0';
  404.    return res;
  405. }
  406.  
  407. /* The function creates an HTML index containing references to given
  408.    directories and files on the appropriate host. The references are
  409.    FTP. */
  410. uerr_t
  411. ftp_index(const char *file, urlinfo *u, struct fileinfo *f)
  412. {
  413.    FILE *fp;
  414.    struct tm *ptm;
  415.    static char *months[] = {
  416.       "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  417.       "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  418.    };
  419.    char *upwd;
  420.    char *htclfile;    /* HTML-clean file name. */
  421.  
  422.    if (!opt.dfp)
  423.    {
  424.       fp = fopen(file, "w");
  425.       if (!fp)
  426.       {
  427.      if (!opt.quiet)
  428.         fprintf(opt.lfile, "%s: %s\n", file, mystrerror(errno));
  429.      return FOPENERR;
  430.       }
  431.    }
  432.    else
  433.       fp = opt.dfp;
  434.    if (u->user)
  435.    {
  436.       char *tmpu, *tmpp;        /* Temporary, clean user and
  437.                    passwd. */
  438.       tmpu = CLEANDUP(u->user);
  439.       tmpp = u->passwd ? CLEANDUP(u->passwd) : NULL;
  440.       upwd = (char *)nmalloc(strlen(tmpu)
  441.                  + (tmpp ? (1 + strlen(tmpp)) : 0) + 2);
  442.       sprintf(upwd, "%s%s%s@", tmpu, tmpp ? ":" : "", tmpp ? tmpp : "");
  443.       free(tmpu);
  444.       if (tmpp)
  445.      free(tmpp);
  446.    }
  447.    else
  448.       upwd = nstrdup("");
  449.    fprintf(fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
  450.    fprintf(fp,
  451.        "<html>\n<head>\n<title>Index of /%s on %s:%d</title>\n</head>\n",
  452.        u->dir, u->host, u->port);
  453.    fprintf(fp, "<body>\n<h1>Index of /%s on %s:%d</h1>\n<hr>\n",
  454.        u->dir, u->host, u->port);
  455.    fprintf(fp, "<pre>\n");
  456.    while (f)
  457.    {
  458.       fprintf(fp, "  ");
  459.       if (f->tstamp != -1)
  460.       {
  461.      ptm = localtime((time_t *)&f->tstamp);
  462.      fprintf(fp, "%d %s %02d ", ptm->tm_year + 1900, months[ptm->tm_mon],
  463.          ptm->tm_mday);
  464.      if (ptm->tm_hour)
  465.         fprintf(fp, "%02d:%02d  ", ptm->tm_hour, ptm->tm_min);
  466.      else
  467.         fprintf(fp, "       ");
  468.       }
  469.       else
  470.      fprintf(fp, "time unknown       ");
  471.       switch (f->type)
  472.       {
  473.      case PLAINFILE:
  474.         fprintf(fp, "File        ");
  475.         break;
  476.      case DIRECTORY:
  477.         fprintf(fp, "Directory   ");
  478.         break;
  479.      case SYMLINK:
  480.         fprintf(fp, "Link        ");
  481.         break;
  482.      default:
  483.         fprintf(fp, "Not sure    ");
  484.         break;
  485.       }
  486.       htclfile = html_quote_string(f->name);
  487.       fprintf(fp, "<a href=\"ftp://%s%s:%hu", upwd, u->host, u->port);
  488.       if (*u->dir != '/')
  489.      putc('/', fp);
  490.       fprintf(fp, "%s", u->dir);
  491.       if (*u->dir)
  492.      putc('/', fp);
  493.       fprintf(fp, "%s", htclfile);
  494.       if (f->type == DIRECTORY)
  495.      putc('/', fp);
  496.       fprintf(fp, "\">%s", htclfile);
  497.       if (f->type == DIRECTORY)
  498.      putc('/', fp);
  499.       fprintf(fp, "</a> ");
  500.       if (f->type == PLAINFILE)
  501.      fprintf(fp, " (%s bytes)", legible(f->size));
  502.       else if (f->type == SYMLINK)
  503.      fprintf(fp, "-> %s", f->linkto ? f->linkto : "(nil)");
  504.       putc('\n', fp);
  505.       free(htclfile);
  506.       f = f->next;
  507.    }
  508.    fprintf(fp, "</pre>\n</body>\n</html>\n");
  509.    free(upwd);
  510.    if (!opt.dfp)
  511.       fclose(fp);
  512.    else
  513.       fflush(fp);
  514.    return FTPOK;
  515. }
  516.  
  517.